#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <regex.h>

#include "eval-upmc.h"

/* ***
 * DEFINITION: 
 *  The name of a variable can contain only letters 
 *  ( a to z or A to Z), numbers ( 0 to 9) or the underscore 
 *  character ( _). 
 *
 * SOURCE: 
 *  http://www.tutorialspoint.com/unix/unix-using-variables.htm
 * ***/
#define SHELLVAR_REGEX "\\$([_]|[a-zA-Z]|[0-9])+"


#define MAX_DOUBLE_IN_STR_SIZE 32


/* ************** *
 * Parser Section *
 * ************** */

static int globalReadOffset;

// Text to read:
static const char *globalInputText = "";

Expression* result_expr;

int parse_error_flag = 0;
int eval_error_flag = 0;
char* error_message = NULL;

int yyparse();

/* ***
 *              cp :  the next char in the command line. 
 * shellvar_length :  if the next command arg is a shellvar, shellvar_length
 *                    give the length of it.
 *
 * return : -1 => error.
 *           0 => next command arg is not a shellvar.
 *           1 => next command arg is a shellvar.
 * ***/
int compute_shellvar_length(char* cp, int* shellvar_length);

void parse(char* cp);



int is_shellvar(char* cp){
  int is_shellvar = 0;

  if(*cp == '$' && (is_arithmetic_operation(cp) == 0)){
    is_shellvar = (interpret_shellvar(cp, &cp) == NULL) ? 0 : 1;
  }

  return is_shellvar;
}

int is_arithmetic_operation(char* cp){
  int is_arithmetic_operation = 0;

  if(*cp == '$' && *(cp+1) == '['){
    is_arithmetic_operation = 1;
  }

  

  return is_arithmetic_operation;
}

char* interpret_shellvar(char* cp, char** new_cp){
  int shellvar_length = 0;
  char* shellvar = NULL;
  char* sv_interpretation = NULL;
  char* allocated_sv_interpretation = NULL;
  int valids_args = 1;

  /* Check that arguments are valids. */
  if(cp == NULL){
    valids_args = 0;
  }
  

  /* Try to found a shellvar */
  if(valids_args){
    if(compute_shellvar_length(cp, &shellvar_length) == 1){
      
      /* put $<var_name> string  into shellvar variable */
      shellvar = (char*) malloc(sizeof(char)*(shellvar_length)+1);
      strncpy(shellvar, cp, shellvar_length);
      shellvar[shellvar_length] = '\0';

      /* remove trailer '$' character */
      shellvar++;

      /* allocate memory for the shellvar_interpretation */

      sv_interpretation = getenv(shellvar);
      allocated_sv_interpretation = 
	(char*) malloc(sizeof(char)*(strlen(sv_interpretation)+1));
      memcpy(allocated_sv_interpretation, 
	     sv_interpretation, 
	     strlen(sv_interpretation));
      allocated_sv_interpretation[strlen(sv_interpretation)] = '\0';
      
      if(new_cp != NULL){
	*new_cp = (cp + shellvar_length);
      }
    }
  }
  

  
  return allocated_sv_interpretation;
}

char* interpret_arithmetic_operation(char* cp, char** new_cp){
  double operation_result = 0;
  char*  allocated_operation_result = NULL;
  char* cp_tmp= cp;
  int can_continue = 1;
  char* operation= NULL;
  int operation_size = 0;


  /* reset */
  parse_error_flag = 0;
  eval_error_flag = 0;


  /* We checking arguments */
  if(cp == NULL){
    can_continue = 0;
  }


  
  if(can_continue){
    
    /* we compute new_cp */
    if(new_cp != NULL){
      while(*cp != ']'){
	cp++;
	operation_size++;
      }

      *new_cp = ++cp;
      operation_size++;
    }

    /* isolating the operation */
    operation = (char*) malloc(sizeof(char)*(operation_size+1));
    memcpy(operation, cp_tmp, operation_size);
    
    operation[operation_size] = '\0';
    
    

    /* parse and eval */
    parse(operation);

    if(parse_error_flag == 0){

      printf("Message from eval-upmc.c:interpret_arithmetic_operation: \n");
      printf("We just show here that we've done Q6.b: simplificator. \n");
      printf("Case (e / e) = 1 is not handled.\n");
      printf("Simplified expr -->");
      display_expression(simplify(result_expr));
      printf("\n");

      result_expr = simplify(result_expr);
      operation_result = eval(result_expr);
      

      if(eval_error_flag == 0){

	int nb_write; /* for know where put '\0' */

	/* need a better idea because MAX_DOUBLE_IN_STR_SIZE is
	   may be not enought... */
	allocated_operation_result = 
	  (char*) malloc(sizeof(char)*MAX_DOUBLE_IN_STR_SIZE);

	if(operation_result == ((int) operation_result)){ 
	  /* then we got an integer */
	  nb_write= sprintf(allocated_operation_result, "%d",
			    (int) operation_result);
	}
	else{
	  /* then we got an double */
	  nb_write= sprintf(allocated_operation_result, "%f",
			    (double)operation_result);
	}

	allocated_operation_result[nb_write] = '\0';
      }
    }
  }

  if(parse_error_flag||eval_error_flag){
    allocated_operation_result = 
      (char*) malloc(sizeof(char)*strlen(error_message)+1);
    
    memcpy(allocated_operation_result, 
	   error_message, 
	   strlen(error_message));
    
    allocated_operation_result[strlen(error_message)] = '\0';
  }

  return allocated_operation_result;
}

int compute_shellvar_length(char* cp, int* shellvar_length){
  
  regex_t regex;
  int can_continue = 1;
  int return_val = -1;
  int regcomp_result = 0;
  int regexec_result = 0;
  regmatch_t pmatch;
  
  

  /* Check that arguments are valids. */
  if(cp == NULL ||
     shellvar_length == NULL){

    return_val = -1;
    can_continue = 0;
  }




  /* Compute the regex. */
  if(can_continue){
    
    regcomp_result = regcomp(&regex, SHELLVAR_REGEX, REG_EXTENDED);
    
    if(regcomp_result != 0){
      
      return_val = -1;
      can_continue = 0;
    }
  }



  /* Search matching str. */
  if(can_continue){

    regexec_result = regexec(&regex, cp, 1, &pmatch, 0);
    
    if(regexec_result != 0){

      if(regexec_result == REG_NOMATCH){
	return_val = 0;
      }
      else{
	return_val = -1;
      }

      can_continue = 0;
    }
    
  }



  /* compute varshell informations */
  if(can_continue){
    *shellvar_length = (pmatch.rm_eo - pmatch.rm_so);
    return_val = 1;
  }


  regfree(&regex);

  return return_val;
}

void parse(char* cp){
  globalInputText = cp;
  globalReadOffset = 0;
  yyparse();
}

int readInputForLexer(char *buffer, int *numBytesRead, int maxBytesToRead) {
  int numBytesToRead = maxBytesToRead;
  int bytesRemaining = strlen(globalInputText)-globalReadOffset;
  int i;

  if ( numBytesToRead > bytesRemaining ) { numBytesToRead = bytesRemaining; }
  for ( i = 0; i < numBytesToRead; i++ ) {
    buffer[i] = globalInputText[globalReadOffset+i];
  }
  *numBytesRead = numBytesToRead;
  globalReadOffset += numBytesToRead;
  return 0;
}






/* ****************** *
 * Evaluator Section  *
 * ****************** */


double eval(Expression* e)
{
  double result = -1;

  e = simplify(e);

  if(IS_INTEGER(e)){
    result = (double) INT_VALUE(e);
  }
  else if(IS_DOUBLE(e)){ 
    result = (double) DOUBLE_VALUE(e);
  }
  else if(IS_BINARY_OPERATION(e)){

    double left_eval = eval(LEFT(e));
    double right_eval = eval(RIGHT(e));

    if((eval_error_flag == 0) && IS_ADDITION(e)){
      result = left_eval + right_eval;
    }
    else if ((eval_error_flag == 0) && IS_SUBSTRACTION(e)){
      result = left_eval - right_eval;
    }
    else if((eval_error_flag == 0) && IS_MULTIPLICATION(e)){
      result = left_eval * right_eval;
    }
    else if((eval_error_flag == 0) && IS_DIVISION(e)){
      
      if(right_eval == 0){
	eval_error_flag = 1;
	error_message = "Error when eval, division by zero.";
      }
      else{
	result = left_eval / right_eval;
      }
    }
  }

  return result;
} 

Expression* new_integer(int i){
  Expression* e = (Expression*) malloc(sizeof(struct _Expression));

  e->type_expression = Integer;
  e->content.as_constant.int_value = i;
}

Expression* new_double(double b){
  Expression* e = (Expression*) malloc(sizeof(struct _Expression));

  e->type_expression = Double;
  e->content.as_constant.double_value = b;
}

Expression* new_binary_operation(char symbol, Expression* left, Expression* right){
  Expression* e = (Expression*) malloc(sizeof(struct _Expression));

  e->type_expression = BinaryOperation;
  e->content.as_binary_operation.symbol = symbol;
  e->content.as_binary_operation.left = left;
  e->content.as_binary_operation.right = right;
}

void display_expression(Expression* e){

  if(IS_INTEGER(e)){
    printf(" %d ", INT_VALUE(e));
  }
  else if (IS_DOUBLE(e)){
    printf(" %f ", DOUBLE_VALUE(e));
  }
  else if(IS_BINARY_OPERATION(e)){
    printf(" ( ");
    display_expression(LEFT(e));
    printf(" %c ", SYMBOL(e));
    display_expression(RIGHT(e));
    printf(" ) ");
  }
  else {
    printf("An error occur.");
  }
}

Expression* simplify(Expression* e){
  Expression* simplified_expression = e;

  if(IS_ADDITION(e)){

    if(IS_ZERO(LEFT(e)) && IS_ZERO(RIGHT(e))){ /* (0 + 0)  = 0 */
      simplified_expression = LEFT(e);
    }
    else if(IS_ZERO(LEFT(e))){ /* (0 + r)  = r */
      simplified_expression = simplify(RIGHT(e));
    }
    else if (IS_ZERO(RIGHT(e))){ /* (l + 0)  = l */
      simplified_expression = simplify(LEFT(e));
    }
  }
  else if(IS_SUBSTRACTION(e)){

    if(IS_ZERO(LEFT(e)) && IS_ZERO(RIGHT(e))){ /* (0 - 0)  = 0 */
      simplified_expression = LEFT(e);
    }
    else if (IS_ZERO(RIGHT(e))){ /* (l - 0)  = l */
      simplified_expression = simplify(LEFT(e));
    }
  }
  else if(IS_MULTIPLICATION(e)){

    if(IS_ZERO(LEFT(e))){ /* (0 * r)  = 0 */
      simplified_expression = LEFT(e);
    }
    if(IS_ONE(RIGHT(e))){ /* (1 * r)  = r */
      simplified_expression = simplify(LEFT(e));
    }
    else if(IS_ZERO(RIGHT(e))){ /* (l * 0) = 0 */
      simplified_expression = RIGHT(e);
    }
    else if(IS_ONE(LEFT(e))){ /* (l * 1)  = l  */
      simplified_expression = simplify(RIGHT(e));
    }
  }
  else if(IS_DIVISION(e)){

    if(IS_ZERO(LEFT(e))){ /* (0 / r) = 0 */
      simplified_expression = LEFT(e);
    }
    /* not traited : e / e = 1 */
  }


  return simplified_expression;
}
